/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package ua.epam.muzalevskiy.third_project.XMLParsers;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
import ua.epam.muzalevskiy.third_project.knife.Blade;
import ua.epam.muzalevskiy.third_project.knife.BladeLength;
import ua.epam.muzalevskiy.third_project.knife.BladeMaterial;
import ua.epam.muzalevskiy.third_project.knife.BladeWidth;
import ua.epam.muzalevskiy.third_project.knife.Handle;
import ua.epam.muzalevskiy.third_project.knife.HandleMaterial;
import ua.epam.muzalevskiy.third_project.knife.Knife;
import ua.epam.muzalevskiy.third_project.knife.KnifeComparator;
import ua.epam.muzalevskiy.third_project.knife.KnifeHandy;
import ua.epam.muzalevskiy.third_project.knife.KnifeParametersSwitcher;
import ua.epam.muzalevskiy.third_project.knife.KnifeType;
import ua.epam.muzalevskiy.third_project.knife.Units;

/**
 * This class realized logic of <code> SaxParser </code> work.
 *
 * @author Muzalevskiy Vitaliy
 */
public class SaxParser implements XMLParser {

    /**
     * Value of <code> KnifeHandler </code> type that broke XML file on
     * components.
     */
    private KnifeHandler handler;

    @Override
    public void parse(String filename) {
        try {
            XMLReader reader = XMLReaderFactory.createXMLReader();
            handler = new KnifeHandler();
            reader.setContentHandler(handler);
            reader.parse(filename);
        } catch (SAXException | IOException e) {
            System.err.println(e.getMessage());
        }
    }

    @Override
    public List<Knife> getKnifeList() {
        Collections.sort(handler.knifeList, new KnifeComparator());
        return handler.knifeList;
    }

    /**
     * This class realized logic of handling XML file so we could get all it
     * components separately and create new value of <code> Knife </code> type.
     */
    class KnifeHandler implements ContentHandler {

        /**
         * <code> List </code> of <code> Knife </code> type values that 
         * parsed from XML file.
         */
        private List<Knife> knifeList;
        
        /**
         * Information about concrete knife.
         */
        private Knife knife;
        
        /**
         * Accessory value of each parameter of XML file that reformed in 
         * necessary knife parameter.
         */
        private String currentValue;
        
        /**
         * Value that describe position of handler.
         */
        private ParseState state;
        
        /**
         * Knife ID value.
         */
        private String id;
        
        /**
         * Knife type value.
         */
        private KnifeType type;
        
        /**
         * Knife handy value.
         */
        private KnifeHandy handy;
        
        /**
         * Origin of knife.
         */
        private String origin;
        
        /**
         * Knife blade value.
         */
        private Blade blade;
        
        /**
         * Value of blade length.
         */
        private BladeLength bladeLength;
        
        /**
         * Value of blade width.
         */
        private BladeWidth bladeWidth;
        
        /**
         * Value of blade material.
         */
        private BladeMaterial bladeMaterial;
        
        /**
         * Value of blade length units.
         */
        private String bladeLengthUnits;
        
        /**
         * Value of blade width units.
         */
        private String bladeWidthUnits;
        
        /**
         * Knife handle value.
         */
        private Handle handle;
        
        /**
         * Value of handle material.
         */
        private HandleMaterial handleMaterial;
        
        /**
         * Value that describe existence of blood flow.
         */
        private boolean bloodFlow;
        
        /**
         * This parameter tall ass is this knife value.
         */
        private boolean value;

        @Override
        public void setDocumentLocator(Locator locator) {
        }

        @Override
        public void startDocument() throws SAXException {
            knifeList = new ArrayList<Knife>();
        }

        @Override
        public void endDocument() throws SAXException {
        }

        @Override
        public void startPrefixMapping(String prefix, String uri)
                throws SAXException {
        }

        @Override
        public void endPrefixMapping(String prefix) throws SAXException {
        }

        @Override
        public void startElement(String uri, String localName, String qName,
                Attributes atts) throws SAXException {
            switch (localName) {
                case "knife":
                    id = atts.getValue("id");
                    break;
                case "blade":
                    state = ParseState.BLADE;
                    break;
                case "length":
                    bladeLengthUnits = atts.getValue("units");
                    break;
                case "width":
                    bladeWidthUnits = atts.getValue("units");
                    break;
                case "handle":
                    state = ParseState.HANDLE;
                    break;
            }
        }

        @Override
        public void endElement(String uri, String localName, String qName)
                throws SAXException {
            switch (localName) {
                case "knife":
                    knife = new Knife(id, type, handy, origin,
                            blade, handle, value);
                    knifeList.add(knife);
                    break;
                case "type":
                    type = KnifeParametersSwitcher.swithKnifeType(currentValue);
                    break;
                case "handy":
                    handy = KnifeParametersSwitcher
                            .switchKnifeHandy(currentValue);
                    break;
                case "origin":
                    origin = currentValue;
                    break;
                case "blade":
                    blade = new Blade(bladeLength, bladeWidth, bladeMaterial);
                    break;
                case "length":
                    getBladeLangth();
                    break;
                case "width":
                    getBladeWidth();
                    break;
                case "material":
                    switchState();
                    break;
                case "handle":
                    handle = new Handle(handleMaterial, bloodFlow);
                    break;
                case "bloodFlow":
                    bloodFlow = Boolean.valueOf(currentValue);
                    break;
                case "value":
                    value = Boolean.valueOf(currentValue);
                    break;
            }
        }

        @Override
        public void characters(char[] ch, int start, int length)
                throws SAXException {
            currentValue = new String(ch, start, length);
        }

        @Override
        public void ignorableWhitespace(char[] ch, int start, int length)
                throws SAXException {
        }

        @Override
        public void processingInstruction(String target, String data)
                throws SAXException {
        }

        @Override
        public void skippedEntity(String name) throws SAXException {
        }

        /**
         * this method switch the states in which handler is now.
         */
        private void switchState() {
            switch (state) {
                case BLADE:
                    bladeMaterial = KnifeParametersSwitcher
                            .switchBladeMaterial(currentValue);
                    break;
                case HANDLE:
                    handleMaterial = KnifeParametersSwitcher
                            .switchHandleMaterial(currentValue);
                    state = ParseState.BLADE;
                    break;
            }
        }

        /**
         * This method construct the value of <code> BladeWidth </code>
         * value.
         * @throws NumberFormatException 
         */
        private void getBladeWidth() throws NumberFormatException {
            Units widthUnits = KnifeParametersSwitcher
                    .switchUnits(bladeWidthUnits);
            int width = Integer.parseInt(currentValue);
            bladeWidth = new BladeWidth(widthUnits, width);
        }

        /**
         * This method construct the value of <code> BladeLength </code>
         * value.
         * @throws NumberFormatException 
         */
        private void getBladeLangth() throws NumberFormatException {
            Units lengthUnits = KnifeParametersSwitcher
                    .switchUnits(bladeLengthUnits);
            int length = Integer.parseInt(currentValue);
            bladeLength = new BladeLength(lengthUnits, length);
        }
    }
}
